package com.sp.business.normalization.impl.mf2;

import static com.sp.business.normalization.impl.mf2.NodeType.ACTION;
import static com.sp.business.normalization.impl.mf2.NodeType.DESCRIPTION;
import static com.sp.business.normalization.impl.mf2.NodeType.DEST_LIST;
import static com.sp.business.normalization.impl.mf2.NodeType.DEST_TRANSLATED_LIST;
import static com.sp.business.normalization.impl.mf2.NodeType.ENABLED;
import static com.sp.business.normalization.impl.mf2.NodeType.FROM_ZONE;
import static com.sp.business.normalization.impl.mf2.NodeType.GLOBAL;
import static com.sp.business.normalization.impl.mf2.NodeType.INTERFACES;
import static com.sp.business.normalization.impl.mf2.NodeType.INTERFACE_IP;
import static com.sp.business.normalization.impl.mf2.NodeType.LABEL;
import static com.sp.business.normalization.impl.mf2.NodeType.LOGGING;
import static com.sp.business.normalization.impl.mf2.NodeType.MASK;
import static com.sp.business.normalization.impl.mf2.NodeType.NAT_RULES;
import static com.sp.business.normalization.impl.mf2.NodeType.NETWORK_OBJS;
import static com.sp.business.normalization.impl.mf2.NodeType.NETWORK_OBJ_GROUPS;
import static com.sp.business.normalization.impl.mf2.NodeType.NETWORK_OBJ_IP;
import static com.sp.business.normalization.impl.mf2.NodeType.NETWORK_OBJ_TO;
import static com.sp.business.normalization.impl.mf2.NodeType.POLICIES;
import static com.sp.business.normalization.impl.mf2.NodeType.ROUTES;
import static com.sp.business.normalization.impl.mf2.NodeType.ROUTE_DEST;
import static com.sp.business.normalization.impl.mf2.NodeType.ROUTE_GATEWAY;
import static com.sp.business.normalization.impl.mf2.NodeType.ROUTE_INTERFACE;
import static com.sp.business.normalization.impl.mf2.NodeType.SECURITY_RULES;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICES;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_GROUPS;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_ICMP_CODE;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_ICMP_TYPE;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_LIST;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_PORT;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_PORT_TO;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_PROTOCOL;
import static com.sp.business.normalization.impl.mf2.NodeType.SERVICE_TRANSLATED_LIST;
import static com.sp.business.normalization.impl.mf2.NodeType.SOURCE_LIST;
import static com.sp.business.normalization.impl.mf2.NodeType.SOURCE_TRANSLATED_LIST;
import static com.sp.business.normalization.impl.mf2.NodeType.TO_ZONE;
import static com.sp.business.normalization.impl.mf2.NodeType.USERS;
import static com.sp.business.normalization.impl.mf2.NodeType.ZONES;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.business.normalization.NormalizationContext;
import com.sp.business.normalization.RawFile;
import com.sp.business.normalization.impl.BaseNormalizer;
import com.sp.business.normalization.impl.DefaultKeyGenerator;
import com.sp.business.objectmodel.CapabilityKeys;
import com.sp.business.objectmodel.NormalizedData;
import com.sp.generated.xml.firemon.ActionTypes;
import com.sp.generated.xml.firemon.BooleanProperty;
import com.sp.generated.xml.firemon.Database;
import com.sp.generated.xml.firemon.IntegerProperty;
import com.sp.generated.xml.firemon.Interface;
import com.sp.generated.xml.firemon.InterfaceCollection;
import com.sp.generated.xml.firemon.InterfaceReference;
import com.sp.generated.xml.firemon.LogTypes;
import com.sp.generated.xml.firemon.NATRule;
import com.sp.generated.xml.firemon.Network;
import com.sp.generated.xml.firemon.NetworkCollection;
import com.sp.generated.xml.firemon.NetworkObject;
import com.sp.generated.xml.firemon.NetworkObjectTypes;
import com.sp.generated.xml.firemon.NetworkReference;
import com.sp.generated.xml.firemon.NetworkReferenceCollection;
import com.sp.generated.xml.firemon.NormalizedDataSchema;
import com.sp.generated.xml.firemon.Policy;
import com.sp.generated.xml.firemon.PolicyCollection;
import com.sp.generated.xml.firemon.PropertyCollection;
import com.sp.generated.xml.firemon.Route;
import com.sp.generated.xml.firemon.RouteCollection;
import com.sp.generated.xml.firemon.SecurityRule;
import com.sp.generated.xml.firemon.ServiceObject;
import com.sp.generated.xml.firemon.StringProperty;
import com.sp.networking.EndPoint;

public class Mf2Normalizer extends BaseNormalizer {
    private static final Logger logger = LoggerFactory
            .getLogger(Mf2Normalizer.class);

    private static final UUID PRODUCT_VERSION = UUID
            .fromString("7d1199f3-f1fc-4ad0-92e8-3c86448fe562");

    private static final String INTERFACE_FILE = "interface";
    private static final String ROUTE_FILE = "ip_route";
    private static final String SERVICE_FILE = "serviceobject";
    private static final String SERVICE_GROUP_FILE = "sgroupobject";
    private static final String NETWORKOBJECT_FILE = "networkobject";
    private static final String HOSTOBJECT_FILE = "hostobject";
    private static final String GROUPOBJECT_FILE = "groupobject";
    private static final String USER_FILE = "userobject";
    private static final String ADMINS_FILE = "admins";
    private static final String FWRULES_FILE = "fwrules";
    private static final String NATRULES_FILE = "natrules";
    
    private NormalizedDataSchema normalizedDataSchema = null;

    public Mf2Normalizer() {
    }

    @Override
    public boolean supportsProductVersion(UUID productVersionKey) {
        return PRODUCT_VERSION.equals(productVersionKey);
    }

    public Set<UUID> getCapabilities() {
        HashSet<UUID> keys = new HashSet<UUID>();
        keys.add(CapabilityKeys.supportsNormalization);
        keys.add(CapabilityKeys.supportsUsage);
        keys.add(CapabilityKeys.isFirewall);
        keys.add(CapabilityKeys.supportsPolicies);
        return keys;
    }

    @Override
    public NormalizedDataSchema normalize(
            NormalizationContext normalizationContext,
            NormalizedDataSchema keyGenCmpData, List<RawFile> rawFiles)
            throws Exception {
        logger.debug("in: Mf2Normalizer.normalize()");

        if (rawFiles.size() == 0) {
            String msg = "unable to load config files for configset";
            logger.error(msg);
            throw new Exception(msg);
        }

        normalizedDataSchema = createEmptyNormalizedDataSchema();

        try {
            this.normalize(rawFiles);
        } catch (Exception ex) {
            String message = "Failed to normalize Mf2 configuration.";
            logger.error(message, ex);
            // do not insert stack trace, it may be too large to fit that field
            publishStatusRemark(message);
        }

        DefaultKeyGenerator generator = new DefaultKeyGenerator();
        generator.generateKeys(keyGenCmpData, normalizedDataSchema);

        return normalizedDataSchema;
    }

    private void normalize(List<RawFile> rawFiles) throws Exception {
        Mf2ConfFileProcessor processor = new Mf2ConfFileProcessor();

        // normalize interfaces
        BufferedReader ifaceReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, INTERFACE_FILE).getInputStream()));
        TreeNode interfaces = processor.processInterfaces(ifaceReader);

        // normalize routes
        BufferedReader routeReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, ROUTE_FILE).getInputStream()));
        TreeNode routes = processor.processRoutes(routeReader);
        
        // normalize services
        BufferedReader serviceReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, SERVICE_FILE).getInputStream()));
        TreeNode services = processor.processServices(serviceReader);
        
        // normalize service groups
        BufferedReader svcGroupReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, SERVICE_GROUP_FILE).getInputStream()));
        TreeNode svcGroups = processor.processServiceGroups(svcGroupReader);
        
        // normalize network objects: hosts, users, and networkobjects
        BufferedReader networkObjReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, NETWORKOBJECT_FILE).getInputStream()));
        TreeNode networkObjs = processor.processNetworkObjs(networkObjReader);
        BufferedReader hostObjReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, HOSTOBJECT_FILE).getInputStream()));
        TreeNode hostObjs = processor.processHostObjs(hostObjReader);
        BufferedReader usersAsHostReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, USER_FILE).getInputStream()));
        TreeNode usersAsHostObjs = processor.processUsersAsHost(usersAsHostReader);
        
        // add all hosts and user-as-host to networkObjs:
        networkObjs.getChildren().addAll(hostObjs.getChildren());
        networkObjs.getChildren().addAll(usersAsHostObjs.getChildren());

        BufferedReader groupObjReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, GROUPOBJECT_FILE).getInputStream()));
        TreeNode groupObjs = processor.processNetworkObjGroups(groupObjReader);
        
        BufferedReader userReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, USER_FILE).getInputStream()));
        TreeNode users = processor.processUsers(userReader);
        
        BufferedReader adminsReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, ADMINS_FILE).getInputStream()));
        TreeNode admins = processor.processAdmins(adminsReader);
        
        // add all admins to users:
        users.getChildren().addAll(admins.getChildren());
        
        BufferedReader fwrulesReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, FWRULES_FILE).getInputStream()));
        BufferedReader natrulesReader = new BufferedReader(new InputStreamReader(
                findFile(rawFiles, NATRULES_FILE).getInputStream()));
        TreeNode policies = processor.processPolicies(fwrulesReader, natrulesReader);
        
        normalizeInterfaces(interfaces);
        normalizeRoutes(routes);
//        normalizeZones(root.getChildByType(ZONES));
        normalizeServices(services);
        normalizeServiceGroups(svcGroups);
        normalizeNetworkObjects(networkObjs);
        normalizeNetworkObjectGroups(groupObjs);
        normalizeUsers(users);
        normalizePolicies(policies);
    }

    private void normalizeInterfaces(TreeNode interfaces) {
        if (interfaces == null) {
            return;
        }

        InterfaceCollection interfaceCollection = new InterfaceCollection();
        getDatabase().setInterfaces(interfaceCollection);

        // each child is an INTERFACE
        for (TreeNode iface : interfaces.getChildren()) {
            Map<NodeType, String> fields = iface.getChildTextMap();
            String name = iface.getText();
            String mask = fields.get(MASK);
            mask = getMaskAsString(mask);

            Interface intf = createInterface(name, fields.get(INTERFACE_IP), mask);
            intf.setNetworks(new NetworkReferenceCollection());
            interfaceCollection.getInterfaces().add(intf);
            logger.warn("Interface added:" + name);
        }
    }

    private void normalizeZones(TreeNode zones) {
        if (zones == null) {
            return;
        }
        throw new RuntimeException("Zones not implemented.");
    }

    private void normalizeServices(TreeNode services) {
        if (services == null) {
            return;
        }

        // each child is a SERVICE
        for (TreeNode service : services.getChildren()) {
            Map<NodeType, String> map = service.getChildTextMap();
            String name = service.getText();
            String protocol = map.get(SERVICE_PROTOCOL);
            if ("ip".equals(protocol)) {
                createServiceObjectByType(getDatabase(), name, null, null,
                        protocol, "");
            } else if ("tcp".equals(protocol) || "udp".equals(protocol)) {
                String destPort = map.get(SERVICE_PORT);
                String destPortTo = map.get(SERVICE_PORT_TO);
                createServiceObjectByType(getDatabase(), name, destPort,
                        destPortTo, protocol, "");
            } else if ("icmp".equals(protocol)) {
                String icmpType = map.get(SERVICE_ICMP_TYPE);
                String icmpCode = map.get(SERVICE_ICMP_CODE);
                ServiceObject serviceObj = createServiceObjectByType(
                        getDatabase(), name, null, null, protocol, "");
                if (icmpType != null && icmpCode != null) {
                    Integer type = Integer.parseInt(icmpType);
                    serviceObj.setIcmpType(NormalizedData
                            .createIntegerProperty(type));
                    Integer code = Integer.parseInt(icmpCode);
                    serviceObj.setIcmpCode(NormalizedData
                            .createIntegerProperty(code));
                } else {
                    logger.warn("Invalid service object for " + name);
                }
            }
        }
    }

    private void normalizeServiceGroups(TreeNode serviceGroups) {
        if (serviceGroups == null) {
            return;
        }

        // each child is a SERVICE_GROUP
        for (TreeNode serviceGrp : serviceGroups.getChildren()) {
            ServiceObject svcGroup = createServiceGroupObject(getDatabase(),
                    serviceGrp.getText());
            for (TreeNode service : serviceGrp.getChildrenByType(LABEL)) {
                String svcName = service.getText();
                ServiceObject member = (ServiceObject) serviceObjectMap
                        .get(svcName);
                if (member != null) {
                    addMemberToServiceObjectGroup(svcGroup, member);
                } else {
                    logger.warn("Unable to find service matching name '"
                            + svcName + "'");
                }
            }
        }
    }

    private void normalizeRoutes(TreeNode routes) {
        if (routes == null) {
            return;
        }

        getDatabase().setRoutes(new RouteCollection());
        getDatabase().setNetworks(new NetworkCollection());
        HashMap<String, Network> networkMap = new HashMap<String, Network>();

        // each child is a ROUTE
        for (TreeNode routeNode : routes.getChildren()) {
            Map<NodeType, String> map = routeNode.getChildTextMap();
            String dest = map.get(ROUTE_DEST);
            String mask = map.get(MASK);
            String gateway = map.get(ROUTE_GATEWAY);
            String interfaceName = map.get(ROUTE_INTERFACE);
            String metricText = map.get("cost");
            boolean isDirectlyConnected = true;

            String ip = dest + "/" + mask;
            interfaceName = interfaceName == null ? "" : interfaceName;
            
            Route route = createRoute(0, interfaceMap, networkMap, ip, gateway,
                    interfaceName, metricText, isDirectlyConnected);

            if (route != null) {
                getDatabase().getRoutes().getRoutes().add(route);
            }
        }

        // add any networks that were normalized along with the routes to the
        // database
        getDatabase().getNetworks().getNetworks().addAll(networkMap.values());
    }

    private void normalizeNetworkObjects(TreeNode networkObjs) {
        if (networkObjs == null) {
            return;
        }

        // create Any network object:
        createNetworkObject(getDatabase(), "Any", null, null,
                NetworkObjectTypes.ANY, null);

        // each child is a NETWORK_OBJ
        for (TreeNode networkObj : networkObjs.getChildren()) {
            Map<NodeType, String> map = networkObj.getChildTextMap();
            String name = networkObj.getText();
            String xsid = createUtf8Xsid("NetworkObject", name);
            String address = map.get(NETWORK_OBJ_IP);
            String addressTo = map.get(NETWORK_OBJ_TO);
            if (addressTo == null) {
                logger.error("networkObj: " + name + " " + address);
                String subnet = address + "/" + map.get(MASK);
                EndPoint endPoint = EndPoint.parse(subnet, false);
                String ip = endPoint.getAddressString();

                String mask = null;
                NetworkObjectTypes type = null;
                if (endPoint.is32BitMask()) {
                    type = NetworkObjectTypes.HOST;
                } else {
                    type = NetworkObjectTypes.NETWORK;
                    mask = endPoint.getMaskString();
                }
                createNetworkObject(getDatabase(), name, ip, mask, type, "",
                        "", xsid);
            } else {
                createRangeNetworkObject(getDatabase(), name, address,
                        addressTo, "", "", xsid);
            }
        }
    }

    private void normalizeNetworkObjectGroups(TreeNode networkObjGrps) {
        if (networkObjGrps == null) {
            return;
        }

        // each child is a NETWORK_OBJ_GROUP
        for (TreeNode networkObjGrp : networkObjGrps.getChildren()) {
            String grpName = networkObjGrp.getText();
            String grpXsid = createUtf8Xsid("NetworkObjectGroup", grpName);
            NetworkObject groupNetObj = createNetworkGroupObjectImp(
                    getDatabase(), grpName, grpXsid);
            for (TreeNode label : networkObjGrp.getChildrenByType(LABEL)) {
                String xsid = createUtf8Xsid("NetworkObject", label.getText());
                NetworkObject networkObject = (NetworkObject) networkObjectMap
                        .get(xsid);
                if (networkObject != null) {
                    addMemberToNetworkObjectGroup(groupNetObj, networkObject);
                } else {
                    logger.warn("Network Object not found: " + label.getText());
                }
            }
        }
    }

    private void normalizeUsers(TreeNode users) {
        if (users == null) {
            return;
        }

        // each child is a USER
        for (TreeNode user : users.getChildren()) {
            String userName = user.getText();
            createUser(getDatabase(), userName);
        }
    }

    private void normalizePolicies(TreeNode policies) {
        if (policies == null) {
            return;
        }

        getDatabase().setPolicies(new PolicyCollection());

        // each child is a POLICY
        for (TreeNode policyNode : policies.getChildren()) {
            Map<NodeType, String> map = policyNode.getChildTextMap();
            PolicyBuilder policyBuilder = null;

            if (policyNode.getChildByType(GLOBAL) != null) {
                policyBuilder = PolicyBuilder.beginGlobalPolicy();
            } else {
                policyBuilder = PolicyBuilder.beginPolicy(map.get(FROM_ZONE),
                        map.get(TO_ZONE));
            }

            for (TreeNode securityRuleNode : policyNode.getChildByType(
                    SECURITY_RULES).getChildren()) {
                SecurityRule securityRule = normalizeSecurityRule(securityRuleNode);
                if (securityRule != null) {
                    policyBuilder.addSecurityRule(securityRule);
                }
            }

            for (TreeNode natRuleNode : policyNode.getChildByType(NAT_RULES)
                    .getChildren()) {
                NATRule natRule = normalizeNATRule(natRuleNode);
                if (natRule != null) {
                    policyBuilder.addNATRule(natRule);
                }
            }

            Policy policy = policyBuilder.build();
            getDatabase().getPolicies().getPolicies().add(policy);
        }
    }

    private SecurityRule normalizeSecurityRule(TreeNode securityRule) {
        if (securityRule == null) {
            return null;
        }

        Map<NodeType, String> map = securityRule.getChildTextMap();

        LogTypes logType = LogTypes.NONE;
        if ("true".equals(map.get(LOGGING))) {
            logType = LogTypes.LOG;
        }

        ActionTypes action = ActionTypes.REJECT;
        if ("allow".equals(map.get(ACTION))) {
            action = ActionTypes.ACCEPT;
        }

        SecurityRuleBuilder builder = SecurityRuleBuilder
                .beginRule(securityRule.getText());
        builder.disabled(!"true".equals(map.get(ENABLED)))
                .comment(map.get(DESCRIPTION)).logType(logType).action(action);

        for (TreeNode serviceLabel : securityRule.getChildByType(SERVICE_LIST)
                .getChildren()) {
            ServiceObject svcObj = (ServiceObject) serviceObjectMap
                    .get(serviceLabel.getText());
            if (svcObj != null) {
                builder.addService(svcObj);
            } else {
                logger.warn("Service object not found: "
                        + serviceLabel.getText());
            }
        }

        for (TreeNode sourceLabel : securityRule.getChildByType(SOURCE_LIST)
                .getChildren()) {
            NetworkObject networkObj = findNetworkObject(sourceLabel.getText());
            if (networkObj != null) {
                builder.addSrcAddress(networkObj);
            } else {
                logger.warn("Network object not found for source label: "
                        + sourceLabel.getText());
            }
        }

        for (TreeNode destLabel : securityRule.getChildByType(DEST_LIST)
                .getChildren()) {
            NetworkObject networkObj = findNetworkObject(destLabel.getText());
            if (networkObj != null) {
                builder.addDestAddress(networkObj);
            } else {
                logger.warn("Network object not found for dest label: "
                        + destLabel.getText());
            }
        }

        return builder.build();
    }

    private NATRule normalizeNATRule(TreeNode natRule) {
        if (natRule == null) {
            return null;
        }

        Map<NodeType, String> map = natRule.getChildTextMap();
        NATRuleBuilder builder = NATRuleBuilder.beginRule(natRule.getText());

        builder.disabled(!"true".equals(map.get(ENABLED))).comment(
                map.get(DESCRIPTION));

        for (TreeNode label : natRule.getChildByType(SOURCE_LIST).getChildren()) {
            Object netObj = findObjectForNAT(label.getText());
            if (netObj != null) {
                builder.addSource(netObj);
            } else {
                logger.warn("NAT object not found for source label: "
                        + label.getText());
            }
        }

        for (TreeNode label : natRule.getChildByType(DEST_LIST).getChildren()) {
            Object netObj = findObjectForNAT(label.getText());
            if (netObj != null) {
                builder.addDest(netObj);
            } else {
                logger.warn("NAT object not found for dest label: "
                        + label.getText());
            }
        }

        for (TreeNode label : natRule.getChildByType(SERVICE_LIST)
                .getChildren()) {
            String svcName = label.getText();
            ServiceObject svcObj = (ServiceObject) serviceObjectMap
                    .get(svcName);
            if (svcObj != null) {
                builder.addService(svcObj);
            } else {
                logger.warn("Service object not found for service label: "
                        + label.getText());
            }
        }

        for (TreeNode label : natRule.getChildByType(SOURCE_TRANSLATED_LIST)
                .getChildren()) {
            Object netObj = findObjectForNAT(label.getText());
            if (netObj != null) {
                builder.addSourceTrans(netObj);
            } else {
                logger.warn("NAT object not found for source translated label: "
                        + label.getText());
            }
        }

        for (TreeNode label : natRule.getChildByType(DEST_TRANSLATED_LIST)
                .getChildren()) {
            Object netObj = findObjectForNAT(label.getText());
            if (netObj != null) {
                builder.addDestTrans(netObj);
            } else {
                logger.warn("NAT object not found for dest translated label: "
                        + label.getText());
            }
        }

        for (TreeNode label : natRule.getChildByType(SERVICE_TRANSLATED_LIST)
                .getChildren()) {
            String svcName = label.getText();
            ServiceObject svcObj = (ServiceObject) serviceObjectMap
                    .get(svcName);
            if (svcObj != null) {
                builder.addServiceTrans(svcObj);
            } else {
                logger.warn("Service object not found for service translated label: "
                        + label.getText());
            }
        }

        return builder.build();
    }

    private NetworkObject findNetworkObject(String name) {
        String xsid = createUtf8Xsid("NetworkObject", name);
        NetworkObject networkObj = (NetworkObject) networkObjectMap.get(xsid);
        if (networkObj == null) {
            xsid = createUtf8Xsid("NetworkObjectGroup", name);
            networkObj = (NetworkObject) networkObjectMap.get(xsid);
        }
        return networkObj;
    }

    private NetworkObject findObjectForNAT(String name) {
        NetworkObject networkObj = findNetworkObject(name);
        if (networkObj == null) {
            logger.warn("looking up Interface:" + name);
            Interface iface = (Interface) interfaceMap.get(name);
            String ip = iface.getName().getValue();
            String mask = iface.getNetmask().getValue();
            networkObj = createNetworkObject(getDatabase(), name, ip, mask,
                    NetworkObjectTypes.NETWORK, "");
        }
        return networkObj;
    }

    private RawFile findFile(List<RawFile> rawFiles, String fileName)
            throws Exception {
        RawFile file = BaseNormalizer.getRawFileByName(rawFiles, fileName);
        if (file == null) {
            throw new Exception("Unable to find config file [" + fileName + "]");
        }
        return file;
    }

    private Database getDatabase() {
        return normalizedDataSchema.getFireMon().getApplications()
                .getFirewallApplication().getDatabase();
    }

    /*
     * FIXME This is cut and paste from CiscoRouteNormalizer should be in
     * BaseNormalizer? Only change is to UID and Xsid creation and remove String
     * line argument
     */
    private static Route createRoute(int lineNum,
            Map<String, Object> interfaceMap, Map<String, Network> networkMap,
            String ip, String gateway, String interfaceName, String metricText,
            boolean isDirectlyConnected) {
        Route route = new Route();
        route.setUID(UUID.randomUUID().toString());
        route.setXsid("Route" + route.getUID());
        route.setProperties(new PropertyCollection());
        List<StringProperty> properties = route.getProperties().getProperties();

        // ip_address netmask
        StringProperty addressProperty = NormalizedData.createStringProperty(
                null, ip);
        String networkAddress = null;
        if (addressProperty != null) {
            networkAddress = addressProperty.getValue();

            route.setDestination(addressProperty);

            try {
                EndPoint.parseIp(addressProperty.getValue());
            } catch (Exception ex) {
                logger.warn(String
                        .format("The route on line %s has an invalid ip_address netmask '%s'",
                                lineNum, addressProperty.getValue()));
            }
        } else
            logger.warn(String.format(
                    "The route on line %s has a blank ip_address netmask.",
                    lineNum));

        // Interface
        StringProperty property = NormalizedData.createStringProperty(
                "Interface", interfaceName);
        if (property != null) {
            // find the interface with the matching name
            if (interfaceMap.containsKey(property.getValue())) {
                // set the route's interface reference
                Interface interfaceObj = (Interface) interfaceMap.get(property
                        .getValue());

                InterfaceReference interfaceReference = new InterfaceReference();
                interfaceReference.setInterfaceObject(interfaceObj);

                route.setInterface(interfaceReference);

                // add the route's network to the interface's network list, if
                // not already present
                if (!StringUtils.isBlank(networkAddress)) {
                    Network network = null;

                    // does the network already exist in the map?
                    if (networkMap.containsKey(networkAddress)) {
                        // get it
                        network = networkMap.get(networkAddress);
                    } else {
                        // add the network to the network map
                        network = new Network();
                        network.setAddress(addressProperty);

                        String networkUid = networkAddress.replace('/', '-');

                        // TODO fix this
                        // networkUid = CiscoNormalizer.createXsid("Network",
                        // networkUid);

                        network.setUID(networkUid);
                        network.setXsid(networkUid);
                        networkMap.put(networkAddress, network);
                    }

                    // does the interface already have a reference to the
                    // network?
                    if (!containsNetwork(interfaceObj.getNetworks(),
                            networkAddress)) {
                        // add a reference to the network
                        NetworkReference networkReference = new NetworkReference();
                        networkReference.setNetwork(network);
                        interfaceObj.getNetworks().getNetworkReferences()
                                .add(networkReference);
                    }
                }
            } else {
                logger.warn(String
                        .format("The route on line %s specifies an interface named '%s' that could not be found in the normalized interface list",
                                lineNum, property.getValue()));

                properties.add(property);
            }
        } else
            logger.warn(String.format(
                    "The route on line %s has a blank Interface.", lineNum));

        // gateway_ip
        property = NormalizedData.createStringProperty(null, gateway);
        if (property != null)
            route.setGateway(property);
        else
            logger.warn(String.format(
                    "The route on line %s has a blank gateway_ip.", lineNum));

        // metric
        String value = metricText;
        if (StringUtils.isBlank(value))
            value = "0";
        else
            value = value.trim();

        int metric = 0;
        try {
            metric = Integer.parseInt(value);
        } catch (NumberFormatException ex) {
            logger.warn(String.format(
                    "The route on line %s has an invalid metric number '%s'",
                    lineNum, property.getValue()));
        }

        IntegerProperty integerProperty = new IntegerProperty();
        integerProperty.setValue(metric);
        route.setMetric(integerProperty);

        // directly connected?
        BooleanProperty booleanProperty = new BooleanProperty();
        booleanProperty.setValue(isDirectlyConnected);
        route.setIsDirectlyConnected(booleanProperty);

        return route;
    }

    private String createUtf8Xsid(String elementName, String name) {
        return elementName + createSafeUTF8ObjectName(name);
    }

    // support Korea chars
    private static String createSafeUTF8ObjectName(String objectName) {
        String safeObjectName = "";

        // do not strip valid Korea chars, only invalid xml chars
        String unsafeCharacters = "<>&";

        for (char c : objectName.toCharArray()) {
            if (!unsafeCharacters.contains(Character.toString(c)))
                safeObjectName += c;
            else
                logger.debug(String.format(
                        "stripped unsafe xs:ID character %s from %s", c,
                        objectName));
        }
        return safeObjectName;
    }

    private NetworkObject createNetworkGroupObjectImp(Database database,
            String name, String xsid) {
        NetworkObject obj = null;
        if (networkObjectMap.get(xsid) == null) {
            NetworkObject networkObject = new NetworkObject();
            StringProperty sp = NormalizedData.createStringProperty(name);
            networkObject.setName(sp);
            networkObject.setXsid(xsid);
            networkObject.setType(NetworkObjectTypes.NETWORK_OBJECT_GROUP);

            database.getNetworkObjects().getNetworkObjects().add(networkObject);
            networkObjectMap.put(xsid, networkObject);
            obj = networkObject;
        } else {
            return (NetworkObject) networkObjectMap.get(xsid);
        }
        return obj;
    }
    
    public static String getMaskAsString(String mask) {
        long address = 0;
        
        try {
            address = Long.valueOf(mask);
        } catch(NumberFormatException e) {
            logger.error("error parsing mask: " + mask);
            return mask;
        }
        
        StringBuffer sb = new StringBuffer(15);
        for (int shift = 24; shift > 0; shift -= 8) {
            // process 3 bytes, from high order byte down.
            sb.append(Long.toString((address >>> shift) & 0xff));
            //sb.append( Long.toString( (address << shift) & 0xff ));
            sb.append('.');
        }
        sb.append(Long.toString(address & 0xff));
        return sb.toString();
    }
}
